﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Input;
using System.Drawing;
using OpenTK.Graphics;
//using Common;
//using AntTweakBar;
using Common;
using System.Threading;
using AntTweakBar;

namespace T17_rotations {
    class T17_rotationsGameWindow : GameWindow {
        public T17_rotationsGameWindow()  {
            this.Load += T17_rotationsGameWindow_Load;
            this.Resize += T17_rotationsGameWindow_Resize;
            this.RenderFrame += T17_rotationsGameWindow_RenderFrame;
            this.Unload += T17_rotationsGameWindow_Unload;
            this.Width = 1024;
            this.Height = 768;
            this.Title = "T17_rotationsGameWindow";
            FpsCounter.FrameMillsecondTick += FpsCounter_FrameMillsecondTick;
        }

        private void FpsCounter_FrameMillsecondTick(double obj) {
            Console.WriteLine($"{obj}ms/frame");
        }
        private void T17_rotationsGameWindow_RenderFrame(object sender, FrameEventArgs e) {
            GL.ClearColor(.1f, .1f,.1f, .9f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            Render();
            context.Draw();
            SwapBuffers();
            ProcessEvents();
        }

        Vao vao;
        Shader shader;
        Texture2D texture;
        ModelMesh mesh ;
        VertexAttribManager attribs = new VertexAttribManager();


        private void T17_rotationsGameWindow_Load(object sender, EventArgs e) {
            InitializeBars();
            VSync = VSyncMode.Off;
            GL.Enable(EnableCap.DepthTest);
            GL.DepthFunc(DepthFunction.Less);
            GL.Enable(EnableCap.CullFace);

            vao = new Vao();
            shader = new Shader("shader.vs", "shader.fs");
            texture = Texture2D.Load("uvmap.dds");
            mesh = MeshLoader.LoadObjIndexed("suzanne.obj");
            mesh.CreateVbo();
            attribs.AddAttrib(3, mesh.VertexBuffer);
            attribs.AddAttrib(2, mesh.UvBuffer);
            attribs.AddAttrib(3, mesh.NormalBuffer);


        }

        Context context;
        Vector3 orientation;
        Vector3 position = new Vector3(-1.5f, 0, 0);
        Vector3 position2 = new Vector3(1.5f, 0, 0);
        Quaternion orientation2=Quaternion.Identity;
        bool lookAtOther = true;
        FloatVariable EulerX, EulerY, EulerZ, PosX, PosY, PosZ;
        QuaternionVariable QuaternionVar;
        BoolVariable UseLookAt;
        private void InitializeBars() {
            context = new Context(Tw.GraphicsAPI.OpenGL);
            var EulerGUI = new Bar(context,"refresh=0.1") { Label = "Euler Settings", Contained = true };
            var QuaternionGUI = new Bar(context) { Label = "Quaternion settings", Position = new Point(808, 16) };


            EulerX = new FloatVariable(EulerGUI, orientation.X) { Label = "Euler X", Step = 0.01f };
            EulerX.Changed += delegate {
                orientation.X = EulerX.Value;
            };
            EulerY = new FloatVariable(EulerGUI, orientation.Y) { Label = "Euler Y", Step = .01f };
            EulerY.Changed += delegate { orientation.Y = EulerY.Value; };
            var EulerZ = new FloatVariable(EulerGUI, orientation.Z) { Label = "Euler Z", Step = .01f };
            EulerZ.Changed += delegate { orientation.Z = EulerZ.Value; };

            PosX = new FloatVariable(EulerGUI, position.X) { Label = "Pos X", Step = .1f };
            PosX.Changed += delegate { position.X = PosX.Value; };
            PosY = new FloatVariable(EulerGUI, position.Y) { Label = "Pos Y", Step = .1f };
            PosX.Changed += delegate { position.Y = PosX.Value; };
            PosZ = new FloatVariable(EulerGUI, position.Z) { Label = "Pos Z", Step = .1f };
            PosX.Changed += delegate { position.Z = PosX.Value; };

            QuaternionVar = new QuaternionVariable(QuaternionGUI, orientation2.X, orientation2.Y, orientation.Z, orientation2.W)
            { Label = "Quaternion", ShowValue = true, Visible = true };
            QuaternionVar.Changed += delegate { this.orientation2 = new Quaternion(QuaternionVar.X, QuaternionVar.Y, QuaternionVar.Z, QuaternionVar.W); };

            UseLookAt = new BoolVariable(QuaternionGUI, lookAtOther) { Label = "Use LookAT", Help = "Look at the other monkey" };
            UseLookAt.Changed += delegate { lookAtOther = UseLookAt.Value; };
        }

        protected override void OnMouseDown(MouseButtonEventArgs e) {
            base.OnMouseDown(e);

            if (HandleMouseClick(context, e)) {
                return;
            }

            if (e.Button == MouseButton.Left) {
            }

            else if (e.Button == MouseButton.Right) {
            }
        }


        protected override void OnMouseUp(MouseButtonEventArgs e) {
            base.OnMouseDown(e);

            if (HandleMouseClick(context, e)) {
                return;
            }
        }
        IDictionary<OpenTK.Input.MouseButton, Tw.MouseButton> Mapping = new Dictionary<OpenTK.Input.MouseButton, Tw.MouseButton>()
            {
                { OpenTK.Input.MouseButton.Left, Tw.MouseButton.Left },
                { OpenTK.Input.MouseButton.Middle, Tw.MouseButton.Middle },
                { OpenTK.Input.MouseButton.Right, Tw.MouseButton.Right },
            };
        private bool HandleMouseClick(Context context, MouseButtonEventArgs e) {


            var action = e.IsPressed ? Tw.MouseAction.Pressed : Tw.MouseAction.Released;

            if (Mapping.ContainsKey(e.Button)) {
                return context.HandleMouseClick(action, Mapping[e.Button]);
            }
            else {
                return false;
            }
        }
        protected override void OnMouseMove(MouseMoveEventArgs e) {
            base.OnMouseMove(e);

            if (context.HandleMouseMove(e.Position)) {
                return;
            }
        }

        protected override void OnResize(EventArgs e) {
            base.OnResize(e);
            context.HandleResize(ClientSize);
        }
        protected override void Dispose(bool manual) {
            if (context != null) {
                context.Dispose();
            }
            base.Dispose(manual);
        }

        private void Render() {
            InputControls.LockCursor = false;
            InputControls.computeMatricesFromInput(this);
            
            shader.Use();

            var projection = Matrix4.CreatePerspectiveFieldOfView(GLUtility.ToRadians(45f), 4f / 3f, .1f, 100f);
            var view = Matrix4.LookAt(new Vector3(0, 0, 7),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0));

            texture.Active(TextureUnit.Texture0, shader, "myTextureSampler");

            float deltaTime = (float)InputControls.getDeltaTime();
            attribs.Enable();
            mesh.IndexBuffer.Bind();
            var lightPos = new Vector3(4, 4, 4);
            shader.SetVector3("LightPosition_worldspace", lightPos);
            {
                orientation.Y += (float)(Math.PI / 2.0f * deltaTime);
                EulerY.Value = orientation.Y;
                //orientation.Y = 1.0f;
                Matrix4 rotation = Matrix4.CreateFromQuaternion(Quaternion.FromEulerAngles(orientation.X, orientation.Y, orientation.Z));
                var translation = Matrix4.CreateTranslation(position);
                var scaling = Matrix4.CreateScale(1f, 1f, 1f);
                var model = scaling * rotation * translation;
                var mvp = model * view * projection;
                shader.SetMatrix4("MVP", ref mvp);
                shader.SetMatrix4("M", ref model);
                shader.SetMatrix4("V", ref view);
                GL.DrawElements(BeginMode.Triangles, mesh.Indices.Length, DrawElementsType.UnsignedShort, 0);
                attribs.Disable();
            }
            {
                //deltaTime = 0.06f;

                if (lookAtOther) {
                    Vector3 desiredDir = position - position2;
                    Vector3 desiredUp = new Vector3(0, 1, 0);
                    Quaternion targetOrientation = Quaternion.Normalize(QuaternionUtil.LookAt(desiredDir, desiredUp));
                    orientation2 = QuaternionUtil.RotateTowards(orientation2, targetOrientation, 1.0f * deltaTime);
                    QuaternionVar.X = orientation2.X;
                    QuaternionVar.Y = orientation2.Y;
                    QuaternionVar.Z = orientation2.Z;
                    QuaternionVar.W = orientation2.W;

                }
                orientation2.ToAxisAngle(out Vector3 axis, out float angle);
                var rotation = Matrix4.CreateFromAxisAngle(axis,angle);
                //rotation = Matrix4.Invert(rotation);
                var translate = Matrix4.CreateTranslation(position2);
                var scaling = Matrix4.CreateScale(1, 1, 1);
                var model = scaling * rotation * translate;

                var mvp = model * view * projection;
                shader.SetMatrix4("MVP", ref mvp);
                shader.SetMatrix4("V", ref view);
                shader.SetMatrix4("M", ref model);

                attribs.Enable();
                GL.DrawElements(BeginMode.Triangles, mesh.Indices.Length, DrawElementsType.UnsignedShort, 0);
                attribs.Disable();

            }
            
        }


        private void T17_rotationsGameWindow_Resize(object sender, EventArgs e) {
            GL.Viewport(0, 0, Width, Height);
        }


        private void T17_rotationsGameWindow_Unload(object sender, EventArgs e) {
            GlDisposableObjectBase.DisposeObjects(vao, shader, texture,mesh);
        }

    }
}
